package com.wmx.wmxredis.exceotion;

import com.wmx.wmxredis.resultAPI.ResultCode;
import com.wmx.wmxredis.resultAPI.ResultData;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;

/**
 * 系统全局统一异常处理
 * 1、@RestController 标记的类，相当于是类中的所有方法上都加了 @ResponseBody。
 * 2、@RestControllerAdvice 默认是针对所有的控制器，但也可以指定某个包，及其子包都会进行拦截，比如 @RestControllerAdvice(basePackages = "com.wmx")
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2021/12/28 15:03
 */
@RestControllerAdvice
public class CommonExceptionHandler {

    private static final Logger log = LoggerFactory.getLogger(CommonExceptionHandler.class);

    /**
     * 1、@Validated 对 RequestBody DTO 参数校验未通过时会抛出 MethodArgumentNotValidException 异常。
     *
     * @param ex
     * @return
     */
    @ExceptionHandler({MethodArgumentNotValidException.class})
    @ResponseStatus(HttpStatus.OK)
    public ResultData<Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        log.error(ex.getMessage(), ex);
        BindingResult bindingResult = ex.getBindingResult();
        StringBuilder sb = new StringBuilder();
        //遍历校验未通过的字段与错误信息.
        for (FieldError fieldError : bindingResult.getFieldErrors()) {
            sb.append(fieldError.getField()).append("：").append(fieldError.getDefaultMessage()).append(", ");
        }
        if (sb.length() > 0) {
            sb.delete(sb.lastIndexOf(","), sb.length());
        } else {
            sb.append(ex.getMessage());
        }
        //封装后的效果如：password：长度需要在6和18之间, userName：不能为null
        String msg = sb.toString();
        return ResultData.error(ResultCode.PARAM_IS_FAIL, msg);
    }

    /**
     * 1、@Validated 对 RequestParam、PathVariable 传参校验未通过时，会抛出 ConstraintViolationException 异常。
     * 2、请求体参数 @RequestBody 如果不是对 DTO 对象属性的校验，则未通过时同样走的 ConstraintViolationException 异常。
     * 比如 @RequestBody @NotEmpty List<Map<String, Object>> dataList
     *
     * @param ex
     * @return
     */
    @ExceptionHandler({ConstraintViolationException.class})
    @ResponseStatus(HttpStatus.OK)
    public ResultData<Object> handleConstraintViolationException(ConstraintViolationException ex) {
        log.error(ex.getMessage(), ex);
        return ResultData.error(ResultCode.PARAM_IS_FAIL, ex.getMessage(), ExceptionUtils.getStackTrace(ex));
    }

    /**
     * 对没有具体捕获的异常统一捕获处理
     *
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.OK)
    public ResultData<Object> handleException(Exception e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        String stackTrace = ExceptionUtils.getStackTrace(e);
        log.error("请求地址'{}',发生系统异常:{}", requestURI, stackTrace);
        return ResultData.error(null, stackTrace);
    }

}
